from view.endSrc.MySqlConn import MySqlConn
from view.endSrc.DBConfig import DBConfig
from view.endSrc.MyTools import MyTools

import numpy as np
from numpy import genfromtxt


class tHighDensityFilter:

    def setSqlConn(self, dbConn: MySqlConn):
        '''
            let use set sql connector dynamatically
        :param dbConn:
        :return:
        '''
        if dbConn is not None:
            self._dbconn = dbConn
            self._logger = dbConn.logger

    def __init__(self, dbconn=None):
        self._dbconn = None
        self._logger = None
        self.setSqlConn(dbconn)

        self.id = None
        self.clusterEstimatorId = None       # related to which record of the table t...ClustersEstimatorBy1D
        self.outputIds = None                # list/np.ndarray_1D
        self.filterClassName = None
        self.fromFilterId = None             # can be None
        self.whichComponent = None
        self.filterParams = None             # JSON dict
        self.clusterEstimatorClassName = None

        self.m_childList = None                # store child filter records, each is of type tHighDensityFilter

    def getLogger(self):
        return self._logger

    def __str__(self):
        '''
            for simple test
        :return:
        '''
        tmp = '-- tHDF --\n'
        tmp += 'id: ' + str(self.id) + '\n'
        tmp += 'fromFilterId: ' + str(self.fromFilterId) + '\n'
        tmp += 'outputIds: ' + str(self.outputIds) + '\n'
        tmp += 'filterParams: ' + str(self.filterParams) + '\n'
        tmp += 'filterClassName: ' + str(self.filterClassName) + '\n'

        tmp += 'clusterEstimatorId: ' + str(self.clusterEstimatorId) + '\n'
        tmp += 'whichComponent: ' + str(self.whichComponent) + '\n'
        tmp += 'clusterEstimatorClassName: ' + str(self.clusterEstimatorClassName) + '\n'

        if self.m_childList is None:
            return tmp

        tmp += '------->'
        for d in self.m_childList:
            tmp += str(d)
        return tmp

    def createTable(self):

        assert self._dbconn is not None, 'please set setSqlConn first'

        if self._dbconn.tableExists('tHighDensityFilter'):
            print('exist..tHighDensityFilter, if you want to drop it, please call MySqlConn.dropTable')
            return True

        sql = """create table tHighDensityFilter(
                id int primary key auto_increment not null,
                clusterEstimatorId int not null,
                whichComponent int not null,
                outputIds JSON,
                filterClassName CHAR(200),
                fromFilterId int,
                filterParams JSON,
                clusterEstimatorClassName CHAR(200) not null ) """

        # foreign key(clusterEstimatorId) references tGammaClustersEstimatorBy1D(id)), do not use it for development

        if not self._dbconn.createTable(sql):
            self._logger.write('tHighDensityFilter.createTable is failed')
            return False
        return True

    def _checkExist(self, id):

        sql = "SELECT * FROM tHighDensityFilter WHERE id = %d" % id
        rows = self._dbconn.read(sql)
        if rows is None or len(rows) == 0:
            self._logger.write('tHighDensityFilter._checkExist..failed to read by id=' + str(id))
            return False

        return True

    def createRow(self, clusterEstimatorClassName, clusterEstimatorId, whichComponent, fromFilterId=None):

        assert self._dbconn is not None, 'please set setSqlConn first'

        assert isinstance(clusterEstimatorClassName, str)
        assert isinstance(clusterEstimatorId, int) and clusterEstimatorId > 0
        assert isinstance(whichComponent, int) and whichComponent >= 0
        assert fromFilterId is None or \
               (isinstance(fromFilterId, int) and self._checkExist(fromFilterId))

        rtup = [clusterEstimatorClassName,
                clusterEstimatorId,
                whichComponent]
        sql = "INSERT INTO tHighDensityFilter( " \
              "clusterEstimatorClassName, clusterEstimatorId, whichComponent)" \
              "VALUES ('%s', %d, %d)" % tuple(rtup)

        if fromFilterId is not None:
            rtup.append(fromFilterId)
            sql = "INSERT INTO tHighDensityFilter( " \
                  "clusterEstimatorClassName, clusterEstimatorId, whichComponent," \
                  "fromFilterId)" \
                  "VALUES ('%s', %d, %d, %d)" % tuple(rtup)

        newId = self._dbconn.insertRetId(sql)
        if newId is not None:
            self.id = newId
            self.clusterEstimatorId = clusterEstimatorId
            self.whichComponent = whichComponent
            self.fromFilterId = fromFilterId
            self.clusterEstimatorClassName = clusterEstimatorClassName

            # fill following fields later
            self.outputIds = None
            self.filterClassName = None
            self.filterParams = None
        else:
            self._logger.write('tHighDensityFilter.createRow: failed to insert..' + str(rtup))
        return newId

    def updateOutputIds(self, outputIds, filterClassName, filterParamsJSONDict):

        assert self._dbconn is not None, 'please set setSqlConn first'
        assert self.id > 0, 'must have a valid DB id'
        assert filterClassName is not None
        assert MyTools.isJsonDumpable(filterParamsJSONDict)

        MyTools.checkMemberShape(outputIds, (), 'tFilter.updateOutputIds.outputIds')
        for i in outputIds:
            assert isinstance(i, int), 'tFilter.updateOutputIds..all members of outputIds must be of type int'

        idsDict = {'ids': list(outputIds)}          # list<int>
        rtup = (MyTools.getJsonByDict(idsDict),
                filterClassName,
                MyTools.getJsonByDict(filterParamsJSONDict),
                self.id)

        sql = """ UPDATE tHighDensityFilter SET 
                  outputIds='%s', filterClassName='%s', 
                  filterParams='%s' WHERE id=%d """ % rtup

        if not self._dbconn.insDelUpd(sql):
            self._logger.write('tHighDensityFilter.updateOutputIds DB updating is failed for id= ' + self.id)
            return False

        self.outputIds = outputIds
        self.filterClassName = filterClassName
        self.filterParams = filterParamsJSONDict

        return True

    def _fromRow(self, row):

        self.id, self.clusterEstimatorId, self.whichComponent, \
        self.outputIds, self.filterClassName, \
        self.fromFilterId, self.filterParams, self.clusterEstimatorClassName = row

        if self.outputIds is not None:
            idsDict = MyTools.getDicByJson(self.outputIds)
            self.outputIds = idsDict['ids']  # list<int>

        # self.filterParams is already a dict, but we do not know the keys/values

    def readRow(self, id):
        '''
                get the iHighDensityFilter instance whose id = id
        :param id: DB id
        :return: True if ok
        '''

        assert self._dbconn is not None, 'please set setSqlConn first'
        assert isinstance(id, int) and id > 0

        #   id int primary key auto_increment not null,
        #   clusterEstimatorId int not null,
        #   whichComponent int not null,
        #   outputIds JSON,
        #   filterClassName CHAR(200),
        #   fromFilterId int,
        #   filterParams JSON

        sql = "SELECT * FROM tHighDensityFilter WHERE id = %d" % id
        rows = self._dbconn.read(sql)
        if rows is None or len(rows) == 0:
            self._logger.write('tFilter.readRow..failed to read by id=' + str(id))
            return False

        self._fromRow(rows[0])
        return True

    def readTree(self, id):
        '''
            return the iHighDensityFilter instance whose id = id, and
            all sub-instances whose fromFilterId = id
        :param id: the id of the root filter
        :return: True if ok
        '''

        assert self._dbconn is not None, 'please set setSqlConn first'
        assert isinstance(id, int) and id > 0

        # 1) ready myself
        if not self.readRow(id):
            self._logger.write('tFilter.readTree..failed to read by id= ' + str(id))
            return False

        # 2) read first-level children
        sql = "SELECT id FROM tHighDensityFilter WHERE fromFilterId = %d" % id
        rows = self._dbconn.read(sql)
        if rows is None:
            self._logger.write('tFilter.readTree..failed to read children ids fromFilterId= ' + str(id))
            return False

        if len(rows) == 0:      # this is a leaf node
            return True

        self.m_childList = []
        for r in rows:
            cid = r[0]
            child = tHighDensityFilter(self._dbconn)

            if not child.readTree(cid):
                self._logger.write('tFilter.readTree..failed to read childTree by id= ' + str(cid))
                return False

            self.m_childList.append(child)

        return True

    def readForestFrom(self, clusterEstimatorId, clusterEstimatorClassName):
        '''
             return ALL instances of iHighDensityFilter, which are filters
             for the cluster estimator (clusterEstimatorClassName) whose id = clusterEstimatorId
        :param clusterEstimatorId:
        :param clusterEstimatorClassName:
        :return: list (sored in whichComponent) or None
        '''

        assert self._dbconn is not None, 'please set setSqlConn first'
        assert isinstance(clusterEstimatorId, int) and clusterEstimatorId > 0
        assert isinstance(clusterEstimatorClassName, str)

        # find roots of different components for this clusterEstimatorId
        sql = "SELECT id FROM tHighDensityFilter WHERE "\
              " clusterEstimatorId = %d and clusterEstimatorClassName = '%s' "\
              " and fromFilterId is NULL " % (clusterEstimatorId, clusterEstimatorClassName)

        rows = self._dbconn.read(sql)
        if rows is None or len(rows) == 0:
            self._logger.write('tFilter.readForestFrom..failed to read roots for clusterEstimatorId= ' + str(clusterEstimatorId))
            return None

        rootList = []
        for r in rows:
            rootId = r[0]
            root = tHighDensityFilter(self._dbconn)

            if not root.readTree(rootId):
                self._logger.write('tFilter.readForestFrom..failed to read the root by id= ' + str(rootId))
                return None

            rootList.append(root)

        rootList.sort(key=lambda x: x.whichComponent)

        return rootList  # may be None for development?

    def getRootsFrom(self, clusterEstimatorId, clusterEstimatorClassName):
        '''
            return instances of iHighDensityFilter, which are roots according to
            different mixture components of the cluster estimator (clusterEstimatorClassName)
            whose id = clusterEstimatorId
        :param clusterEstimatorId:
        :param clusterEstimatorClassName:
        :return: list (sored by the field whichComponent)  or None
        '''

        assert self._dbconn is not None, 'please set setSqlConn first'
        assert isinstance(clusterEstimatorId, int) and clusterEstimatorId > 0
        assert isinstance(clusterEstimatorClassName, str)

        sql = "SELECT * FROM tHighDensityFilter WHERE " \
              " clusterEstimatorId = %d and clusterEstimatorClassName = '%s' " \
              " and fromFilterId is NULL " % (clusterEstimatorId, clusterEstimatorClassName)

        rows = self._dbconn.read(sql)
        if rows is None or len(rows) == 0:
            self._logger.write('tFilter.readForestFrom..failed to read roots for clusterEstimatorId= ' +
                               str(clusterEstimatorId) + ' and clusterEstimatorClassName=' + clusterEstimatorClassName)
            return False

        rootList = []
        for row in rows:
            root = tHighDensityFilter(self._dbconn)
            root._fromRow(row)    # filled data from memory row
            rootList.append(root)

        rootList.sort(key=lambda x: x.whichComponent)

        return rootList

    def getFilterDetailByEst(self, clusterEstimatorClassName, clusterEstimatorId, whichComp):

        assert self._dbconn is not None, 'please set setSqlConn first'
        assert isinstance(clusterEstimatorId, int) and clusterEstimatorId > 0
        assert isinstance(clusterEstimatorClassName, str)

        sql = "SELECT * FROM tHighDensityFilter WHERE " \
              " clusterEstimatorClassName = '%s' and clusterEstimatorId = %d and whichComponent = %d" % \
              (clusterEstimatorClassName, clusterEstimatorId, whichComp)

        rows = self._dbconn.read(sql)
        if rows is None or len(rows) == 0:
            self._logger.write('tFilter.getFilterDetailByEst..failed to read records for clusterEstimatorId= ' +
                               str(clusterEstimatorId) + ' and clusterEstimatorClassName=' + clusterEstimatorClassName)
            return []

        return rows